﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Borg.AttributeAnalyze.Result;

namespace Borg.AttributeAnalyze
{
    public class AttributeAnalyzer
    {
        private IList<FileInfo> _discoveryListIntern;

        private readonly AttributeAnalyzeRuntime _runtime =
            new AttributeAnalyzeRuntime(new RuntimeConfiguration());

        public List<ResultBase> Analyze(string borgPath, IList<FileInfo> discoveryList, IList<string> assemblySearchPath)
        {
            var resultBases = new List<ResultBase>();
            var slotDefinitions = new List<SlotDefinition>();

            _discoveryListIntern = discoveryList;
            _discoveryListIntern.Insert(0, new FileInfo(Path.Combine(borgPath, "Borg.dll")));
            
            AssemblyAnalyzer.AssemblySearchPath = assemblySearchPath;
            AssemblyAnalyzer.OnAssemblyResolved += AssemblyAnalyzer_OnAssemblyResolved;
            
            foreach (var fileInfo in _discoveryListIntern)
            {
                ContractInfo[] contractInfos;
                PluginInfo[] pluginInfos;

                var file = fileInfo;
                var analyzer = new AssemblyAnalyzer(delegate { });
                var builder = new TypeBuilder();
                
                resultBases.AddRange(analyzer.GetMissingAtributes(file));
                analyzer.AnalyzeFile(file.FullName, builder, out contractInfos, out pluginInfos);
                
                foreach (var ci in contractInfos)
                {
                    _runtime.AddInitialContract(ci);
                    slotDefinitions.AddRange(ci.SlotDefinitions);
                }

                foreach (var pi in pluginInfos)
                    _runtime.AddInitialPlugin(pi);
            }

            resultBases.AddRange(AnalyzeSlotDefinition(slotDefinitions));
            resultBases.AddRange(AnalyzeSlot());
            resultBases.AddRange(AnalyzePlug());
            resultBases.AddRange(AnalyzeExtension());
            
            return resultBases;
        }

        public List<ResultBase> Analyze(string borgPath, string[] discoveryList, string[] assemblySearchPath)
        {
            var fileInfos = (from file in discoveryList select new FileInfo(file)).ToList<FileInfo>();
            return Analyze(borgPath, fileInfos, assemblySearchPath.ToList());
        }

        private IEnumerable<ResultBase> AnalyzeExtension()
        {
            var invalidHandler = new List<ResultBase>();
            var dictionary = new Dictionary<string, ExtensionTypeInfo>();
            
            foreach (var eti in _runtime.Repository.ExtensionTypeInfos)
            {
                eti.VerifyEventHandlerMethods(false, invalidHandler);

                if (dictionary.ContainsKey(eti.Name))
                    invalidHandler.Add(new DuplicatedExtensionName(eti, dictionary[eti.Name]));
                else
                    dictionary.Add(eti.Name, eti);

                if (eti.Type.GetConstructor(new Type[0]) == null)
                    invalidHandler.Add(new MissingDefaultCtor(eti));
            }

            return invalidHandler;
        }

        private IEnumerable<ResultBase> AnalyzePlug()
        {
            var invalidHandler = new List<ResultBase>();

            using (var enumerator = _runtime.Repository.PlugTypeInfos.GetEnumerator())
            {
                PlugTypeInfo plug;

                while (enumerator.MoveNext())
                {
                    plug = enumerator.Current;
                    var slotDefinition = _runtime.Repository.GetQualifiedSlotDefinition(plug.Name);

                    if (slotDefinition == null)
                        invalidHandler.Add(new MissingSlotDefinition(plug));
                    else
                    {
                        foreach (var definition in slotDefinition.ParamDefinitions)
                        {
                            var qualifiedParamValue = plug.GetQualifiedParamValue(definition.Name);

                            if (qualifiedParamValue == null)
                            {
                                if (definition.HasDefaultValue)
                                {
                                    try
                                    {
                                        Convert.ChangeType(definition.DefaultValue, definition.Type);
                                    }
                                    catch (Exception)
                                    {
                                        invalidHandler.Add(new InvalidDefaultValue(plug, definition));
                                    }
                                }
                                else
                                    invalidHandler.Add(new MissingParameter(plug, definition));
                            }
                            else
                            {
                                try
                                {
                                    Convert.ChangeType(qualifiedParamValue.Value, definition.Type);
                                }
                                catch (Exception)
                                {
                                    invalidHandler.Add(new InvalidParameterValue(plug, definition, qualifiedParamValue));
                                }
                            }
                        }

                        if (plug.ExtensionTypeInfo.Type.GetInterface(slotDefinition.TypeName) == null)
                            invalidHandler.Add(new MissingInterface(plug, slotDefinition));
                    }

                    plug.VerifyEventHandlerMethods(false, invalidHandler);

                    var paramValueResult = new ConcurrentBag<ResultBase>();
                    var paramValues = new ConcurrentDictionary<string, ParamValue>();
                    
                    plug.ParamValues.AsParallel().ForAll<ParamValue>(delegate (ParamValue param)
                    {
                        if (string.IsNullOrWhiteSpace(param.Name))
                            paramValueResult.Add(new MissingParameterName(plug, param));
                        else
                        {
                            if ((slotDefinition != null) &&
                                (slotDefinition.ParamDefinitions.Find(param.Name).Length == 0))
                                paramValueResult.Add(new InvalidParameter(plug, param));

                            if (!paramValues.TryAdd(param.Name, param))
                                paramValueResult.Add(new DuplicatedParameterValue(plug, param));
                        }
                    });

                    invalidHandler.AddRange(paramValueResult);
                }
            }

            return invalidHandler;
        }

        private IEnumerable<ResultBase> AnalyzeSlot()
        {
            var invalidHandler = new List<ResultBase>();

            foreach (var sti in _runtime.Repository.SlotTypeInfos)
            {
                if (_runtime.Repository.GetQualifiedSlotDefinition(sti.Name) == null)
                {
                    if (string.IsNullOrWhiteSpace(sti.DerivedFrom))
                        invalidHandler.Add(new MissingSlotDefinition(sti));
                    else if (_runtime.Repository.GetQualifiedSlotDefinition(sti.DerivedFrom) == null)
                        invalidHandler.Add(new MissingSlotDefinition(sti));
                }

                sti.VerifyEventHandlerMethods(false, invalidHandler);
            }

            return invalidHandler;
        }

        private static IEnumerable<ResultBase> AnalyzeSlotDefinition(IEnumerable<SlotDefinition> slotDefinitions)
        {
            var slots = new ConcurrentDictionary<string, SlotDefinition>();
            var result = new ConcurrentBag<ResultBase>();
            
            slotDefinitions.AsParallel().ForAll(delegate (SlotDefinition s)
            {
                if (!slots.TryAdd(s.Name, s))
                    result.Add(new DuplicatedSlotName(s, slots[s.Name]));

                var parameter = new ConcurrentDictionary<string, ParamDefinition>();
                
                s.ParamDefinitions.AsParallel().ForAll(delegate (ParamDefinition paramDef)
                {
                    if (string.IsNullOrWhiteSpace(paramDef.Name))
                        result.Add(new MissingParameterName(s, paramDef));
                    else if (!parameter.TryAdd(paramDef.Name, paramDef))
                        result.Add(new DuplicatedParameterDefinition(s, paramDef));

                    if (!paramDef.HasDefaultValue)
                        return;

                    try
                    {
                        Convert.ChangeType(paramDef.DefaultValue, paramDef.Type);
                    }
                    catch (Exception)
                    {
                        result.Add(new InvalidDefaultValue(s, paramDef));
                    }
                });
            });

            return result;
        }

        private void AssemblyAnalyzer_OnAssemblyResolved(object sender, AssemblyResolvedEventArgs e)
        {
            if (_discoveryListIntern == null)
                return;

            var source = from file in _discoveryListIntern select file.FullName.ToLower();

            foreach (var srchPath in AssemblyAnalyzer.AssemblySearchPath)
            {
                if ((String.Compare(new DirectoryInfo(srchPath).FullName,
                                    e.ResolvedAssembly.Directory.FullName,
                                    StringComparison.OrdinalIgnoreCase) == 0) &&
                    !source.Contains(e.ResolvedAssembly.FullName.ToLower()))
                    _discoveryListIntern.Add(e.ResolvedAssembly);
            }
        }
    }
}

